// Functions only, no DOMContentLoaded event listener here.
// This file is meant for use in various page scripts.
// jQuery is required for this script.

class CUN2 {

    // utils
    constructor(
        thread_id = 0,
        thread_author_pk = null,
        my_pk = null,
        server_url = null,
        thread_container_id = "thread-container",
        reply_container_id = "reply-container",
        composer_container_id = "composer-container"
    ) {
        this.thread_id = thread_id;
        this.thread_author_pk = thread_author_pk;
        this.my_pk = my_pk;
        this.server_url = server_url || window.location.origin; // default to current origin (web app mode)
        this.server_url = this.server_url.endsWith('/') ? this.server_url.slice(0, -1) : this.server_url;
        this.thread_container_id = thread_container_id;
        this.reply_container_id = reply_container_id;
        this.composer_container_id = composer_container_id;
        this.submit_reply_function = null;
        this.highlighted_chat_id = null;
        this.auto_scroll = true;
        this.reply_scroll_position = 0;
        this.new_messages = 0;
        this.polling_interval = 2000; // 2 seconds
        this.chat_slide_duration = 100; // 0.3 seconds
        this.chat_slide_count = 5; // 5*100 = 0.5 seconds
        this.polling_ticker = 0;
    }

    start = function(submit_reply_function = null) {
        if (submit_reply_function && typeof submit_reply_function === "function") {
            this.submit_reply_function = submit_reply_function
        } // else, do nothing, likely in embed mode


        this.load_replies();
        init_live_lamports(`${this.server_url}/static/rates.json`);

        // Listen to scroll up event on reply-container and set auto_scroll to false
        $(`#${this.reply_container_id}`).on('scroll', () => {
            const reply_container = $(`#${this.reply_container_id}`);
            this.reply_scroll_position = !isNaN(this.reply_scroll_position) ? this.reply_scroll_position : reply_container.scrollTop();
            const this_scroll_position = reply_container.scrollTop();
            const delta = this.reply_scroll_position - this_scroll_position;
            if (delta > 0) { // User scrolled up
                this.auto_scroll = false;
            } else { // User scrolled down
                // determine if user has scrolled to bottom
                const padding_account = 30;
                if (this_scroll_position + reply_container.height() + padding_account >= reply_container[0].scrollHeight) { // User scrolled to bottom
                    this.scroll_to_bottom(); // just clears new messages and sets auto_scroll to true in this case (scroll is not needed)
                }
            }
            this.reply_scroll_position = this_scroll_position * 1;
        });
    }

    end_session = function() {
        this.my_pk = null;
        this.build_composer_form();
    }

    increment_new_messages = function() {
        if (this.auto_scroll) return; // user will be scrolled to bottom anyway
        this.new_messages += 1;
        const plur = this.new_messages == 1 ? '' : 's';
        const msg = `&nbsp;${this.new_messages} new message${plur}&nbsp;`;
        $('#new_message_dismiss').empty().append(bsi("compact_down"), msg, bsi("compact_down"));
        $('#new_message_dismiss').fadeIn(300);
    }

    clear_new_messages = function() {
        this.new_messages = 0;
        $('#new_message_dismiss').fadeOut(300);
    }

    scroll_to_bottom = function() {
        this.auto_scroll = true;
        this.clear_new_messages();
        const reply_container = $(`#${this.reply_container_id}`);
        $(reply_container).scrollTop(reply_container[0].scrollHeight);
    }

    copy_share_link = function(embed = false) {
        // copy current page url adding ?embed=1 or &embed=1
        const url = new URL(window.location);
        if (embed) url.searchParams.set('embed', '1');
        navigator.clipboard.writeText(url.toString());
        const verb = embed ? 'Embed' : 'Share';
        alert(`${verb} link copied to clipboard!`);
    }

    // async fetchers
    fetch_replies = async function(last_chat_id = null) {

        var endpoint = `${this.server_url}/api/replies/${this.thread_id}`;
        if (last_chat_id) endpoint += `?last_chat_id=${last_chat_id}`;
        // GET replies from free endpoint
        // Wait for response and return JSON, not a promise
        const replies = await fetch(endpoint, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        })
            .then(response => response.json())
            .then(data => {
                if (!data.ok) throw new Error(data.error?.message || 'Unknown error');
                console.log('Replies fetched:', data);
                return data;
            })
            .catch(error => {
                console.error('Error fetching replies:', error);
                return {};
            })
        return replies;
    }

    highlight = function(id = 0) { // scroll to and highlight chat
        this.highlighted_chat_id = id;
        $(`.chat[data-chat-id="${id}"]`).addClass('highlighted');
    }

    remove_highlight = function() {
        this.highlighted_chat_id = null;
        $(`.chat`).removeClass('highlighted');
    }

    show_premium_access_required = function(channel) {
        const container = $(`#${this.reply_container_id}`);
        const composerContainer = $(`#${this.composer_container_id}`);

        // Clear existing content
        container.empty();
        composerContainer.empty();

        // Create premium access message
        const isFree = channel.monthly_lamports_fee === 0;
        const priceDisplay = isFree
            ? '<span class="text-success fs-5">Free</span>'
            : `<span class="text-primary fs-5">${(channel.monthly_lamports_fee / 1000000000).toFixed(4)} SOL</span>`;
        const subscriptionNote = isFree
            ? '<p class="mt-3 mb-0"><small class="text-muted">Free subscription - no payment required</small></p>'
            : '<p class="mt-3 mb-0"><small class="text-muted">Your subscription will be valid for 30 days</small></p>';

        const premiumMessage = $(`
            <div class="text-center py-5">
                <div class="alert alert-warning">
                    <h5 class="alert-heading">🔒 Premium Channel</h5>
                    <p>This thread is in the <strong>${channel.name}</strong> premium channel.</p>
                    <p>You must subscribe to view replies in this channel.</p>
                    <hr>
                    <div class="d-flex justify-content-center align-items-center gap-3 mb-3">
                        <div>
                            <strong>${isFree ? 'Subscription:' : 'Monthly Fee:'}</strong>
                            ${priceDisplay}
                        </div>
                    </div>
                    <a href="${this.server_url}/profile/${channel.owner_public_key}" class="btn btn-primary" target="_blank">
                        Subscribe to ${channel.name}${isFree ? ' (Free)' : ''}
                    </a>
                    ${subscriptionNote}
                </div>
            </div>
        `);

        container.append(premiumMessage);
    }

    load_replies = async function() {
        try {

            // Check if .chat elements exist in the thread container
            // If they do, get the highest chat_id and use it as the last_chat_id
            // Otherwise, do not set last_chat_id
            const chats = $(`.chat`);
            var last_chat_id = 0;
            chats.each(function() {
                const chat_id = parseInt($(this).attr('data-chat-id'));
                if (chat_id > last_chat_id) {
                    last_chat_id = chat_id;
                }
            });

            // Fetch replies from the server
            const data = await this.fetch_replies(last_chat_id);

            // Check if premium access is required
            if (data.needs_premium_access && data.blocked_channel) {
                this.show_premium_access_required(data.blocked_channel);
                return;
            }

            const authors = data.authors;
            const channels = data.channels || {};
            const replies = data.replies.sort((a, b) => a.chat_id - b.chat_id);
            const container = $(`#${this.reply_container_id}`);

            for (var i = 0; i < replies.length; i++) {
                var tip_class = '', tip_pill = '';
                const reply = replies[i];
                const author = authors?.[reply.author_public_key] || { display_name: 'Unknown' };
                if ((reply?.tip_lamports || 0) > 0) {
                    tip_class = ' tip_chat text-warning alert-warning';
                    tip_pill = `<span class="badge rounded-pill text-bg-warning tip_pill live_lamports show_sol float-end" data-lamports="${reply.tip_lamports}"></span>`;
                }
                if ($(`.chat[data-chat-id="${reply.chat_id}"]`).length > 0) {
                    continue;
                }

                // Build channel badge if reply has a channel
                var channelBadge = '';
                if (reply.channel_id && channels[reply.channel_id]) {
                    const channel = channels[reply.channel_id];
                    channelBadge = `<a href="${this.server_url}/channel_redirect/${reply.channel_id}" class="badge rounded-pill bg-primary" title="${channel.description || 'Channel: ' + channel.name}" target="_blank">${channel.name}</a> `;
                }

                const authImg = author?.profile_picture_url ? `<img src="${author.profile_picture_url}" class="rounded-circle" style="width:17px;height:17px;">` : '';

                const replyElement = $(`
                <div class="chat${tip_class}" data-chat-id="${reply.chat_id}" data-author-pk="${reply.author_public_key}" data-reply-to="${reply.reply_to}">
                    <span class="text-muted small">
                        ${reply.chat_id} 
                        <a class="ghostly user-profile-link" href="${this.server_url}/profile/${reply.author_public_key}" title="${reply.author_public_key}" data-public-key="${reply.author_public_key}" target="_blank">
                            ${authImg} ${author.display_name}
                        </a>
                    </span>
                    <span class="float-end">
                        <small class="text-muted relative-time" data-time="${reply.datetime_created}"></small>
                        <a class="chat-options-opener text-muted small" data-type="chat" data-chat-id="${reply.chat_id}">
                            ${bsi('three-dots-vertical')}
                        </a>
                    </span>
                    <br>
                    ${channelBadge}${reply.content}
                    <br>
                    <span class="reaction-container" data-chat-id="${reply.chat_id}">
                        <button class="btn btn-sm btn-outline-secondary text-muted" style="opacity:0.5;">
                            ⮝ ...
                         </button>
                        <button class="btn btn-sm btn-outline-secondary text-muted" style="opacity:0.5;">
                            ⮟ ...
                         </button>
                    </span>
                    <span class="action-link-container float-end"></span>
                    ${tip_pill}
                </div>`);

                const replyLink = $(`<a class="action-btn reply-btn" data-chat-id="${reply.chat_id}" data-author-pk="${reply.author_public_key}"></a>`);
                replyLink.empty().append(bsi("reply"));
                replyLink.on('click', (e) => {
                    e.preventDefault();
                    const target = $(e.target).closest('.reply-btn');
                    this.build_composer_form(target.attr('data-chat-id'), target.attr('data-author-pk'));
                });

                // Add replyLink to action-link-container
                replyElement.find('.action-link-container').append(replyLink);

                // Trigger replyLink if mobile users swipe right on chat
                replyElement.on('swipe', (e) => {
                    e.preventDefault();
                    $(this).find('.reply-btn').trigger('click');
                });

                // Add replyElement to container or to parent chat if it's a reply to another chat
                if (reply.reply_to && reply.reply_to != this.thread_id) {
                    const parentChat = $(`.chat[data-chat-id="${reply.reply_to}"]`);
                    if (parentChat.length > 0) {
                        parentChat.first().append(replyElement);
                    } else {
                        container.append(replyElement);
                    }
                } else {
                    container.append(replyElement);
                }
            }
            update_lamport_elements();
            formatRelativeTime();

            // Update .thread_reply_ticker with the number of replies
            const total_replies = $(`.chat`).length;
            $(`.thread_reply_ticker`).empty().append(total_replies);

            // Move this to highlighting and scrolling interval
            if (this.highlighted_chat_id) {
                const hchat = $(`.chat[data-chat-id="${this.highlighted_chat_id}"]`);
                if (hchat.length > 0) {
                    hchat.addClass('highlighted');
                    hchat[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
                    this.auto_scroll = false; // override auto scroll to allow highlighted chat to stay in view.
                    this.highlighted_chat_id = null; // prevent future scroll-to
                }
            } else if (this.auto_scroll) {
                // smooth scroll to bottom of reply-container
                setTimeout(() => {
                    container.animate(
                        { scrollTop: container[0].scrollHeight },
                        300
                    );
                }, 50);
            } else if (replies.length > 0) {
                this.increment_new_messages();
            }

            // Add follow links (requires follow.js)
            setTimeout(clone_follow_links, 50);

            // Get tip metadata from .chat elements
            var lamports_to_author = 0, lamports_to_others = 0;
            $(`.chat`).each((_index, element) => {
                const chat = $(element);
                const reply_to = chat.attr('data-reply-to');
                const lamports = chat.find('.live_lamports').attr('data-lamports');
                if (!lamports || isNaN(lamports * 1) || lamports * 1 <= 0) return; // continue
                if (reply_to == this.thread_id) { // direct reply to thread
                    lamports_to_author += lamports * 1;
                    return; // continue
                }
                const reply_parent = $(`.chat[data-chat-id="${reply_to}"]`);
                if (reply_parent.length < 1) return; // continue
                const parent_author_pk = reply_parent.attr('data-author-pk');
                if (parent_author_pk == this.thread_author_pk) {
                    lamports_to_author += lamports * 1;
                } else {
                    lamports_to_others += lamports * 1;
                }
            });
            const total_lamports = lamports_to_author + lamports_to_others;
            $('#tip_tracker').remove();
            if (total_lamports > 0) {
                const percent_to_author = total_lamports ? Math.floor((lamports_to_author / total_lamports) * 100) : 100;
                const percent_to_others = 100 - percent_to_author;
                const precent_to_others_str = `
                        <span style="padding-right:5px;" class="float-end">
                            <span id="tips_to_others_ll" class="live_lamports" data-lamports="${lamports_to_others}"></span>
                            <span id="tips_to_others_perc" class="small">(${percent_to_others}% to other)</span>
                        </span>
                `;
                $(`#${this.thread_container_id}`).append(
                    `<div id="tip_tracker" class="text-muted small" style="position:relative;">
                        <div id="tips_to_author_bar" style="width: ${percent_to_author}%;"></div>
                        <div id="tips_to_others_bar" style="left: ${percent_to_author}%;"></div>
                        <span style="padding-left:5px;">
                            <span id="tips_to_author_ll" class="live_lamports" data-lamports="${lamports_to_author}"></span>
                            <span id="tips_to_author_perc" class="small">(${percent_to_author}% to author)</span>
                        </span>
                        ${(percent_to_others > 0) ? precent_to_others_str : ''}
                    </div>`
                );
                update_lamport_elements();
            }

            // Finally, setTimeout to poll for new replies
            this.polling_ticker += 1;
            setTimeout(() => {
                if (!this.thread_id) return; // destroyed
                this.load_replies(); // automatically pulls top chat id
                if ((this.polling_ticker - 1) % 5 == 0) { // every 5 polls
                    loadBatchedReactions();
                }
            }, this.polling_interval);

        } catch (error) {
            console.error('Error loading replies:', error);
        }
    }

    build_composer_form = function(reply_to = null, author_pk = null) { // boostrap 5.3 required
        const composer_container = $(`#${this.composer_container_id}`);
        if (!this.my_pk) {
            const server_home = this.server_url || window.location.origin;
            composer_container.empty().append(`Please <a href="${server_home}">log in</a> to post replies.`);
            return;
        }
        if (composer_container.length < 1) {
            return; // likely in embed mode
        }
        if (!this.submit_reply_function || typeof this.submit_reply_function != 'function') {
            composer_container.empty().append(
                `<div class="alert alert-danger">No submit_reply_function provided.</div>`
            );
            return;
        }
        try {
            reply_to = reply_to || this.thread_id;
            author_pk = author_pk || this.thread_author_pk;
            let resetFormBtn = null;
            const verb = reply_to == this.thread_id ? "Post to Thread" : `Reply to Chat #${reply_to}`;

            // init form
            const currencyToggleStr = window?.SOLANA_RATES_USE_FIAT ? `<a class="btn btn-dark btn-sm action-btn reverse-text-gradient" id="currency-toggle">Tipping&nbsp;in&nbsp;${my_fiat_code()}&nbsp;${bsi('arrow-left-right')}&nbsp;Tip&nbsp;in&nbsp;SOL</a>` : '';
            const tipCurrency = window?.SOLANA_RATES_USE_FIAT ? 'fiat' : 'sol';
            const initStep = tipCurrency == 'sol' ? '0.0001' : '0.01';
            const replyForm = $(`
                <form id="replyForm">
                    <a id="new_message_dismiss" style="text-decoration:none;"></a>
                    <span id="tip_live_lamports" class="live_lamports show_sol reverse-text-gradient d-none float-end" data-lamports="0"></span>
                    <br>
                    <input type="hidden" id="reply_to" name="reply_to" value="${reply_to}">
                    <input type="hidden" id="author_public_key" name="author_public_key" value="${author_pk}">
                    <input type="hidden" id="tip_amount_lamports" name="tip_amount_lamports" value="">
                    <input type="hidden" id="tip_currency" name="tip_currency" value="${tipCurrency}">
                    <div class="input-group" id="reply_form_input_group">
                        <input type="text" class="form-control" id="reply_content" name="reply_content" placeholder="${verb}..." required>
                        <input type="number" class="form-control" id="tip_amount_input" name="not_used_by_server" placeholder="Tip (optional)" step="${initStep}"
                            min="0">
                    </div>
                    <span class="text-muted small"><span id="charCount">0</span>/300</span>
                    <span class="float-end text-muted small">
                        ${currencyToggleStr}
                    </span>
                </form>`);

            // build form
            const currencyToggle = replyForm.find('#currency-toggle');
            if (currencyToggle.length > 0) { // If the user does not have a preferred fiat, do not show the currency toggle as they are locked in with SOL
                currencyToggle.on('click', () => {
                    const tip_amount_input = $('#tip_amount_input');
                    const tip_currency_input = $('#tip_currency');
                    const currency_toggle = $('#currency-toggle');
                    tip_amount_input.val(0).trigger('input'); // clean slate
                    if (tip_currency_input.val() == 'sol') { // switch to fiat mode
                        tip_currency_input.val('fiat');
                        tip_amount_input.val('').attr('step', '0.01');
                        currency_toggle.removeClass('text-gradient').addClass('reverse-text-gradient');
                        currency_toggle.empty().append(`Tipping&nbsp;in&nbsp;${my_fiat_code()}&nbsp;${bsi('arrow-left-right')}&nbsp;Tip&nbsp;in&nbsp;SOL`);
                    } else { // switch to sol mode
                        tip_currency_input.val('sol');
                        tip_amount_input.val('').attr('step', '0.0001');
                        currency_toggle.removeClass('reverse-text-gradient').addClass('text-gradient');
                        currency_toggle.empty().append(`Tipping&nbsp;in&nbsp;SOL&nbsp;${bsi('arrow-left-right')}&nbsp;Tip&nbsp;in&nbsp;${my_fiat_code()}`);
                    }
                });
            }

            // text input listener
            const replyInput = replyForm.find("#reply_content");
            replyInput.on("keydown", function(event) {
                // Submit when Enter is pressed
                if (event.keyCode === 13) {
                    event.preventDefault();
                    sendButton.trigger("click");
                }
            });
            replyInput.on("input", function() {
                const charCount = $(this).val().length;
                replyForm.find("#charCount").text(charCount);
                if (charCount > 300) {
                    $(this).addClass("invalid");
                    sendButton.prop("disabled", true);
                    $('#charCount').addClass("text-danger");
                } else {
                    $(this).removeClass("invalid");
                    sendButton.prop("disabled", false);
                    $('#charCount').removeClass("text-danger");
                }
            });

            // tip amount input listener
            const tipAmountInput = replyForm.find("#tip_amount_input");
            tipAmountInput.on("keydown", function(event) {
                // Submit when Enter is pressed
                if (event.keyCode === 13) {
                    event.preventDefault();
                    setTimeout(() => {
                        sendButton.trigger("click");
                    }, 50);
                }
            });
            tipAmountInput.on("input", function() {
                const val = $(this).val();
                if (!isNaN(val * 1) && val * 1 > 0) {
                    let lamports = 0;
                    const tip_currency = $('#tip_currency').val();
                    $(this).addClass("valid");
                    $('#send_btn').addClass("btn-success");
                    if (tip_currency == 'sol') { // User is typing in sol, show fiat only
                        lamports = val * 1e9;
                    } else { // User is typing in fiat, show sol and fiat
                        try {
                            lamports = fiat_to_lamports(val);
                        } catch (e) {
                            console.error('Error converting fiat to lamports: ', e);
                        }
                    }
                    $('#tip_amount_lamports').val(lamports);
                    $('#tip_live_lamports').attr('data-lamports', lamports).removeClass('d-none');
                    update_lamport_elements();
                } else {
                    $(this).removeClass("valid");
                    $('#send_btn').removeClass("btn-success");
                    $('#tip_live_lamports').attr('data-lamports', 0).addClass('d-none');
                    $('#tip_amount_lamports').val(0);
                    update_lamport_elements();
                }
            });

            // new message dismiss listener
            const dismiss = replyForm.find("#new_message_dismiss");
            dismiss.on("click", () => {
                this.scroll_to_bottom();
            });

            // render
            $(`#${this.composer_container_id}`).empty().append(
                resetFormBtn,
                replyForm
            );

            // buttons
            const sendButton = $('<button id="send_btn" type="submit" class="btn btn-primary form-control" title="Send Reply"></button>');
            sendButton.empty().append(bsi("send"));
            $('#reply_form_input_group').append(sendButton);
            if (reply_to != this.thread_id) {

                // link below resets to thread mode link if in reply to chat mode
                resetFormBtn = $('<button id="reset_form_btn" class="btn btn-danger form-control" title="Reset to thread mode"></button>');
                resetFormBtn.empty().append(`<span style="font-size:0.5em;opacity:0.6;">${reply_to}</span>`, '<br>', bsi("x"), '<br>', `<span style="font-size:0.5em;">&nbsp;</span>`);
                resetFormBtn.on("click", () => { this.build_composer_form(); });
                $('#reply_form_input_group').prepend(resetFormBtn);
            }

            // call class user defined function on submit
            replyForm.on('submit', this.submit_reply_function);

            // focus on reply input
            replyInput.focus();
        } catch (e) {
            console.error(e);
        }
    }

    clear_composer_form = function() {
        // Clear all input fields in the composer form
        $('#reply_content').val('').trigger('input');
        $('#tip_amount_input').val('').trigger('input');
    }

    destroy = function() {
        // Kill setTimeouts with recursive method calls
        this.thread_id = null;
        this.my_pk = null;
        this.server_url = null;
        // remove all event listeners from reply container
        $(`#${this.reply_container_id}`).off();
        // clear intervals if any
        // clear any timeouts if any
        // empty containers
        $(`#${this.reply_container_id}`).empty();
        $(`#${this.composer_container_id}`).empty();
    }
}